//
// Created by Tony on 2024/6/7.
//

#ifndef COMPILER_SYMBOL_TABLE_H
#define COMPILER_SYMBOL_TABLE_H

#include <cstdint>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>

namespace Compiler {

using std::shared_ptr;
using std::string;
using std::unordered_map;
using std::vector;
using std::weak_ptr;

enum class ScopeType : uint16_t { UNKNOWN, CLASS, METHOD, BLOCK, IMPORTS };

enum class SymbolType : uint16_t { UNKNOWN, CLASS, FIELD, METHOD, LOCAL };

struct Location {
    int16_t index;    // index in symbolTable, -1 when not exist
    int16_t distance; // distance from reference scope to define scope
};

struct Symbol {
    string name;
    SymbolType type;
    ScopeType scope;
    int32_t classId; // classId in ClassTable, indicate the type of symbol
    int16_t slotId;  // slotId in frame, index + offset of symbolTable. -1 is invalid
};

class SymbolTable final {
    string mName;
    ScopeType mScopeType;
    int16_t mOffset; // offset in frame
    vector<Symbol> mSymbols;
    // key: symbolName, value: index in symbolTable
    unordered_map<string, int16_t> mIndexes;
    weak_ptr<SymbolTable> mOuter;
    vector<shared_ptr<SymbolTable>> mInners;
    vector<shared_ptr<SymbolTable>> mImports;

public:
    SymbolTable(ScopeType scope, int16_t offset, const shared_ptr<SymbolTable>& outer,
                string name = "anonymous")
        : mScopeType(scope), mOffset(offset), mOuter(outer), mName(std::move(name)) {}

    // no exception, must check no exist then add
    Location add(const string& symbolName, SymbolType type, int32_t classId);
    Symbol get(Location location);
    Location where(const string& symbolName);

    int16_t size() const { return static_cast<int16_t>(mSymbols.size()); }

    shared_ptr<SymbolTable> getOuter() { return mOuter.lock(); }

    int16_t addInner(const shared_ptr<SymbolTable>& symbolTable);
    shared_ptr<SymbolTable> getInner(int16_t index) { return mInners[index]; }
    shared_ptr<SymbolTable> getInner(const string& name);

    int16_t addImport(const shared_ptr<SymbolTable>& import);
    shared_ptr<SymbolTable> getImport(int16_t index) { return mImports[index]; }
    shared_ptr<SymbolTable> getImport(const string& name);

    int16_t maxSlotCount() const;     // self size + max child size
    int16_t totalSymbolCount() const; // sum of self and children size
};

} // namespace Compiler

#endif // COMPILER_SYMBOL_TABLE_H
